#include "actiontimeline.h"

#include <QGraphicsProxyWidget>
#include <QGLWidget>
#include <QPixmap>
#include <QGraphicsPixmapItem>
#include <QGraphicsSceneMouseEvent>
#include <QPushButton>
#include <QEvent>
#include <QTransform>
#include <QToolTip>
#include <QFileInfo>
#include <QMenu>
#include <QStringBuilder>
#include "keyframedialog.h"
#include "actioncommands.h"
#include "global.h"
#include "audiodialog.h"
#include "history.h"

#define INFO_WIDTH 100
#define DEFAULT_SOUND_WIDTH 5000

/*********************************************************/
/*              GUI ACTION TIMELINE CLASS                */
/*********************************************************/
GUIActionTimeline::GUIActionTimeline(QWidget *parent) :
    QGraphicsView(parent),
    _curScale(0.5),
    _scaleMin(0.125),
    _scaleMax(0.5),
    _upperBound(200),
    _lowerBound(0),
    _curTime(0),
    _timelineOffset(0),
    _scrollOption(OFF),
    _curAction(NULL),
    _scene(NULL),
    _ruler(NULL),
    _motionTrack(NULL),
    _soundTrack(NULL),
    _disabledOverlay(NULL),
    _scaleSlider(NULL),
    _timeSpinBox(NULL),
    _scrollTimer(new QTimer())
{
    _grayBrush.setStyle(Qt::SolidPattern);
    _grayBrush.setColor(QColor(240,240,240));

    _scrollTimer->setInterval(40);
    connect(_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimeout()));

    //Set the viewport to to use openGL to speed things up
    setViewport(new QGLWidget());

    initScene();
}


void GUIActionTimeline::resizeEvent(QResizeEvent *event){
    int i, curY, width = this->width();

    _scene->setSceneRect(0,0,width,130);

    //Resize all of the "stretchable" lines
    for(i = 0; i < _stretchableLines.length(); i++){
        curY = _stretchableLines[i]->line().y1();
        _stretchableLines[i]->setLine(0,curY,width,curY);
    }
    //Resize the bottom gray rectangle
    _bottomRect->setRect(0,0,width,25);

    //Resize the disabled overlay
    _disabledOverlay->setRect(0,0,width,this->height());

    //Resize the ruler
    _ruler->setRect(0,0,width-INFO_WIDTH,30);


    //Jiggle the position line to cause an itemChanged event to be fired
    //(just calling setPosition(_curTime) doesn't cause a change in position
    //so the event isn't fired) in order to reposition the line within
    //the valid boundaries.
    _positionLine->setPosition(_curTime - 1);
    _positionLine->setPosition(_curTime + 1);
}


void GUIActionTimeline::loadAction(RobotAction *action){
    QColor motionColor(143,188,143,100);
    QBrush motionBrush(motionColor);

    QTransform scaleTransform;
    scaleTransform.scale(_curScale,1.0);

    _curAction = action;
    connect(action, SIGNAL(timeChanged(int)), this, SLOT(setTime(int)));

    if(_motionTrack != NULL)
        delete _motionTrack;

    //Create the motion track
    _motionTrack = new GUIMotionTrack(action->_motionTrack, this);
    _scene->addItem(_motionTrack);
    _motionTrack->setPos(INFO_WIDTH,31);
    _motionTrack->setRect(0,0,action->duration(), 35);
    _motionTrack->setPen(QPen(QColor(190,190,190)));
    _motionTrack->setBrush(motionBrush);
    _motionTrack->setTransform(scaleTransform);
    connect(_motionTrack, SIGNAL(editingItem(int)), this, SLOT(onEdit(int)));
    connect(_motionTrack, SIGNAL(editingFinished(int)), this, SLOT(onEditFinished(int)));

    _soundTrack = new GUISoundTrack(action->_soundTrack, this);
    _scene->addItem(_soundTrack);
    _soundTrack->setPos(INFO_WIDTH,69);
    _soundTrack->setRect(0,0,action->duration(),35);
    _soundTrack->setPen(Qt::NoPen);
    _soundTrack->setBrush(Qt::NoBrush);
    _soundTrack->setTransform(scaleTransform);
    connect(_soundTrack, SIGNAL(editingItem(int)), this, SLOT(onEdit(int)));
    connect(_soundTrack, SIGNAL(editingFinished(int)), this, SLOT(onEditFinished(int)));
}


void GUIActionTimeline::initScene(){
    int width = this->width();
    QPen positionLinePen(QColor(119,136,153));
    positionLinePen.setWidth(3);

    _scene = new QGraphicsScene(0,0,width,130);

    //Draw the "info area"
    initInfoArea();

    //Create the ruler
    _ruler = new GUITimelineRuler(this);
    _ruler->setRect(0,0,width-INFO_WIDTH,30);
    _ruler->setBrush(_grayBrush);
    //_ruler->setBrush(QBrush(QColor(220,220,220)));
    _ruler->setPen(Qt::NoPen);
    _scene->addItem(_ruler);
    _ruler->setPos(INFO_WIDTH,0);
    connect(_ruler, SIGNAL(rulerClicked(int)), this, SLOT(onRulerClicked(int)));
    connect(_ruler, SIGNAL(mouseMove(int)), this, SLOT(onRulerMouseMove(int)));
    connect(_ruler, SIGNAL(mouseRelease()), this, SLOT(onRulerMouseRelease()));

    //Create the position line
    _positionLine = new GUIPositionLine(this);
    _positionLine->setLine(INFO_WIDTH,23,INFO_WIDTH,112);
    _positionLine->setPen(positionLinePen);
    _positionLine->setZValue(15);
    _scene->addItem(_positionLine);
    connect(_positionLine,SIGNAL(pastBoundary(int)),this, SLOT(onPastBoundary(int)));

    //Add the bottom gray rectangle
    _bottomRect = _scene->addRect(0,0,width,25,Qt::NoPen,_grayBrush);
    _bottomRect->setPos(0,106);

    //Create the overlay for showing the timeline is disabled
    _disabledOverlay = _scene->addRect(0,0,width,this->height(),Qt::NoPen,QBrush(QColor(200,200,200)));
    _disabledOverlay->setOpacity(0.25);
    _disabledOverlay->setZValue(25);
    _disabledOverlay->setVisible(false);

    //Set the scale to the middle
    _scaleSlider->setValue(50);

    setScene(_scene);
}

void GUIActionTimeline::initInfoArea(){
    int width = this->width();
    QGraphicsLineItem* curLine;
    QGraphicsRectItem* curRect;
    QGraphicsTextItem* curText;
    QGraphicsProxyWidget* curProxy;
    QPen linePen(QColor(190,190,190));
    QPen separatorPen(QColor(150,150,150));
    separatorPen.setWidth(3);

    //Draw all of the horizontal, stretchable lines
    curLine = _scene->addLine(0,30,width,30,linePen);
    //Everything needs to have a high z-value, so that this
    //"info" area floats on top of everything else
    curLine->setZValue(15);
    _stretchableLines.append(curLine);
    curLine = _scene->addLine(0,67,width,67,separatorPen);
    curLine->setZValue(15);
    _stretchableLines.append(curLine);
    curLine = _scene->addLine(0,104,width,104,linePen);
    curLine->setZValue(15);
    _stretchableLines.append(curLine);

    //Draw the line that separates the info area from the timeline
    curLine = _scene->addLine(INFO_WIDTH - 1,0,INFO_WIDTH - 1,125,linePen);
    curLine->setZValue(15);

    //Draw the "Motion" info rectangle
    curRect = _scene->addRect(0,0,99,35,Qt::NoPen,_grayBrush);
    curRect->setPos(0,31);
    curText = _scene->addText("Motion");
    curText->setParentItem(curRect);
    curText->setPos(28,8);
    curRect->setZValue(15);

    //Draw the "Audio" info rectangle
    curRect = _scene->addRect(0,0,99,35,Qt::NoPen,_grayBrush);
    curRect->setPos(0,69);
    curText = _scene->addText("Audio");
    curText->setParentItem(curRect);
    curText->setPos(30,8);
    curRect->setZValue(15);

    //Draw the time spin box and connect its signals
    curRect = _scene->addRect(0,0,99,30,Qt::NoPen,_grayBrush);
    curRect->setZValue(15);
    QPixmap pixMap(":/icons/Clock");
    QGraphicsPixmapItem* pixMapItem = _scene->addPixmap(pixMap);
    pixMapItem->setScale(0.35);
    pixMapItem->setPos(8,7);
    pixMapItem->setZValue(15);
    curText = _scene->addText("s");
    curText->setZValue(15);
    curText->setPos(81,6);
    _timeSpinBox = new QDoubleSpinBox();
    _timeSpinBox->setSingleStep(0.05);
    _timeSpinBox->setMaximum(360.0);
    curProxy = _scene->addWidget(_timeSpinBox);
    curProxy->setPos(30,5);
    curProxy->setZValue(15);
    connect(_timeSpinBox, SIGNAL(valueChanged(double)), this, SLOT(onSpinBoxChanged(double)));

    //Draw the scaling slider and connect its signals
    _scaleSlider = new QSlider(Qt::Horizontal);
    _scaleSlider->setToolTip("Adjust timeline scale");
    curProxy = _scene->addWidget(_scaleSlider);
    curProxy->resize(75,10);
    curProxy->setPos(12,108);
    curProxy->setZValue(15);
    connect(_scaleSlider, SIGNAL(valueChanged(int)), this, SLOT(onSliderChanged(int)));

    //Add the "add sound" button
    QIcon soundIcon(":/icons/Audio Wave");
    QPushButton* addSoundBtn = new QPushButton(soundIcon,"");
    addSoundBtn->setIconSize(QSize(15,15));
    addSoundBtn->setToolTip("Add sound");
    curProxy = _scene->addWidget(addSoundBtn);
    curProxy->setPos(105,106);
    curProxy->setZValue(10);
    connect(addSoundBtn, SIGNAL(clicked()), this, SLOT(onSoundBtnClicked()));

    //Add the "Add key frame" button
    QIcon keyFrameIcon(":/icons/Key Frame");
    QPushButton* addKeyFrameBtn = new QPushButton(keyFrameIcon,"");
    addKeyFrameBtn->setIconSize(QSize(15,15));
    addKeyFrameBtn->setToolTip("Add key frame");
    curProxy = _scene->addWidget(addKeyFrameBtn);
    curProxy->setPos(134,106);
    curProxy->setZValue(10);
    connect(addKeyFrameBtn, SIGNAL(clicked()), this, SLOT(onKeyFrameBtnClicked()));
}

void GUIActionTimeline::setTime(int msec){
    int corrected = msec < 0 ? 0 : msec;
    //Round the time to the nearest 10 msec
    corrected = ROUND_TO(corrected, 10);

    //The line will emit a signal if setting it to the new position
    //will cause it to scroll past the boundaries of the timeline
    //This signal is caught and scrolls the timeline.  See OnPastBoundary()
    _positionLine->setPosition(corrected);

    if(_curAction != NULL && sender() != _curAction){
        //setTime wasn't called by the action, so set the time of the action,
        //but block signals so that this function isn't called an extra time
        _curAction->blockSignals(true);
        _curAction->setCurrentTime(corrected);
        _curAction->blockSignals(false);
    }

    if(sender() != _timeSpinBox){
        //Set the spinner box value, but block signals
        _timeSpinBox->blockSignals(true);
        _timeSpinBox->setValue(MSEC_TO_SECF(corrected));
        _timeSpinBox->blockSignals(false);
    }

    _curTime = corrected;
}

void GUIActionTimeline::onPastBoundary(int amount){
    //Called when the position line is past the scroll boundaries
    //Scroll the timeline appropriately (amount is the amount in msec
    //past the boundary)
    _timelineOffset += amount;
    if(_timelineOffset < 0)
        _timelineOffset = 0;

    //Redraw the ruler
    _ruler->update();
}

void GUIActionTimeline::setScale(float scale){
    _curScale = scale;

    //Redraw the ruler
    _ruler->update();
    //Redraw the position line
    _positionLine->setPosition(_curTime);
    //Scale the tracks
    QTransform scaleTransform;
    scaleTransform.scale(_curScale,1.0);

    if(_motionTrack != NULL)
        _motionTrack->setTransform(scaleTransform);
    if(_soundTrack != NULL)
        _soundTrack->setTransform(scaleTransform);
}

void GUIActionTimeline::onSliderChanged(int newVal){
    if(_curAction != NULL && _curAction->state() == QAbstractAnimation::Running){
        //Pause the animation if it is running
        _curAction->pause();
    }

    //Convert the integer value (between 0 and 100) to a
    //float representing the scale
    setScale(calculateScale(newVal));
}

float GUIActionTimeline::calculateScale(int val){
    int sliderRange = _scaleSlider->maximum() - _scaleSlider->minimum();
    float valAsPercent = (float)val / (float)sliderRange;
    float scaleRange = _scaleMax - _scaleMin;
    float scaleVal = _scaleMax - (valAsPercent * scaleRange);

    return scaleVal;
}


void GUIActionTimeline::onSpinBoxChanged(double val){
    int msec = SEC_TO_MSECI(val);

    //Set the timeline to the position
    setTime(msec);
}


void GUIActionTimeline::onRulerClicked(int msec){
    //Allow the click to move the line past default playback boundaries
    int prevUpper = _upperBound;
    int prevLower = _lowerBound;
    _upperBound = 0;
    _lowerBound = 0;
    setTime(msec);
    _upperBound = prevUpper;
    _lowerBound = prevLower;
}

void GUIActionTimeline::onRulerMouseMove(int msec){
    if(((msec > _curTime) && _scrollOption == RIGHT) || ((msec < _curTime) && _scrollOption == LEFT))
        return;

    //Allow the move to go past the default playback boundaries.
    int prevUpper = _upperBound;
    int prevLower = _lowerBound;
    _upperBound = 0;
    _lowerBound = 0;
    setTime(msec);
    _upperBound = prevUpper;
    _lowerBound = prevLower;

    //Determine if the move takes place past the edge of the timeline
    //If so, set a timer to scroll the timeline
    int lowerBound = 0;
    int upperBound = width() - INFO_WIDTH;

    if(_positionLine->pos().x() <= lowerBound){
        scroll(LEFT);
    }
    else if(_positionLine->pos().x() >= upperBound){
        scroll(RIGHT);
    }
    else{
        stopScroll();
    }
}

void GUIActionTimeline::scroll(ScrollOption option){
    _scrollOption = option;
    _scrollTimer->start();
}

void GUIActionTimeline::stopScroll(){
    _scrollOption = OFF;
    _scrollTimer->stop();
}

void GUIActionTimeline::onRulerMouseRelease(){
    //Stop scrolling
    stopScroll();
}

void GUIActionTimeline::onScrollTimeout(){
    if(_scrollOption == LEFT){
        int prevUpper = _upperBound;
        int prevLower = _lowerBound;
        _upperBound = 0;
        _lowerBound = 0;
        setTime(_curTime - (15 / _curScale));
        _upperBound = prevUpper;
        _lowerBound = prevLower;
    }
    else if(_scrollOption == RIGHT){
        int prevUpper = _upperBound;
        int prevLower = _lowerBound;
        _upperBound = 0;
        _lowerBound = 0;
        setTime(_curTime + (15 / _curScale));
        _upperBound = prevUpper;
        _lowerBound = prevLower;
    }
}


void GUIActionTimeline::onKeyFrameBtnClicked(){
    if(_motionTrack == NULL)
        emit needAction();

    //You can't edit the first key frame
    if(_curTime != 0){
        //Pass an edit request to the motion track
        _motionTrack->onEditRequest(_curTime);
    }
}

void GUIActionTimeline::onSoundBtnClicked(){
    if(_soundTrack == NULL)
        emit needAction();

    //Find out what sound we are over, and return its timestamp
    QList<SoundInfo> soundInformation = _curAction->soundInformation();
    for(int i = 0; i < soundInformation.length(); i++){
        if((_curTime >= soundInformation[i].timestamp) &&
           (_curTime <= soundInformation[i].duration)){

            _soundTrack->onEditRequest(soundInformation[i].timestamp);
            return;
        }
    }

    _soundTrack->onEditRequest(_curTime);
}

void GUIActionTimeline::onEdit(int timestamp){
    QObject* track = sender();

    setTime(timestamp);
    setDisabled(true);

    if(track == _motionTrack){
        emit editingKeyFrame(timestamp);
    }
    else if(track == _soundTrack){
        emit editingSound(timestamp);
    }
}

void GUIActionTimeline::onEditFinished(int timestamp){
    setDisabled(false);

    emit editingFinished();
}


void GUIActionTimeline::changeEvent(QEvent * event){
    //Implement a custom behavior for the enabledChanged event
    //to draw an overlay on the timeline to indicate it is disabled
    if(event->type() == QEvent::EnabledChange){
        if(this->isEnabled() && _disabledOverlay != NULL){
            _disabledOverlay->setVisible(false);
        }
        else if(_disabledOverlay != NULL){
            _disabledOverlay->setVisible(true);
        }
    }

    QGraphicsView::changeEvent(event);
}


/*********************************************************/
/*               GUI POSITION LINE CLASS                 */
/*********************************************************/
GUIPositionLine::GUIPositionLine(GUIActionTimeline* timeline):
    _timeline(timeline)
{
    //setFlag(QGraphicsItem::ItemIsMovable);
    setFlag(QGraphicsItem::ItemSendsScenePositionChanges);
}

int GUIPositionLine::positionToMsec(){
    return ((this->pos().x()) / (_timeline->curScale()) + _timeline->offset());
}

void GUIPositionLine::setPosition(int msec){
    //Convert the msec to pixel position
    int pixelPosition = (msec - _timeline->offset()) * _timeline->curScale();

    this->setPos(pixelPosition, 0);
}

void GUIPositionLine::mouseMoveEvent(QGraphicsSceneMouseEvent *event){
    QGraphicsItem::mouseMoveEvent(event);
}

void GUIPositionLine::mousePressEvent(QGraphicsSceneMouseEvent *event){
    QGraphicsItem::mousePressEvent(event);
}

void GUIPositionLine::mouseReleaseEvent(QGraphicsSceneMouseEvent *event){
    QGraphicsItem::mouseReleaseEvent(event);
}

QVariant GUIPositionLine::itemChange(GraphicsItemChange change, const QVariant &value){
    if(change == ItemPositionChange){
        //restrict the movement to the horizontal axis only
        QPoint newPos = value.toPoint();
        newPos.setY(0);

        //Calculate if it is past the scroll boundaries
        int lowerBound = _timeline->lowerBound();
        int upperBound = _timeline->width() - INFO_WIDTH - _timeline->upperBound();

        if(newPos.x() < lowerBound){
            emit pastBoundary((newPos.x() - lowerBound) / _timeline->curScale());
            newPos.setX(lowerBound);
        }
        else if(newPos.x() > upperBound){
            emit pastBoundary((newPos.x() - upperBound) / _timeline->curScale());
            newPos.setX(upperBound);
        }

        return newPos;
    }
    return QGraphicsItem::itemChange(change, value);
}


/*********************************************************/
/*               GUI TIMELINE RULER CLASS                */
/*********************************************************/
GUITimelineRuler::GUITimelineRuler(GUIActionTimeline* timeline):
    _timeline(timeline),
    _tickInterval(50),
    _majorTickInterval(500),
    _tickHeight(5),
    _majorTickHeight(15)
{}


void GUITimelineRuler::setTickInterval(int msec){
    _tickInterval = msec;
    update();
}

void GUITimelineRuler::setMajorTickInterval(int msec){
    _majorTickInterval = msec;
    update();
}

void GUITimelineRuler::setTickHeight(int px){
    _tickHeight = px;
    update();
}

void GUITimelineRuler::setMajorTickHeight(int px){
    _majorTickHeight = px;
    update();
}

void GUITimelineRuler::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget){
    QGraphicsRectItem::paint(painter, option, widget);
    drawTicks(painter);
}

void GUITimelineRuler::drawTicks(QPainter *painter){
    if(!scene())
        return;

    float scale = _timeline->curScale();
    int i, numOfTicks, curTickPosition, tickHeight;
    int timelineOffset = _timeline->offset(), pixelOffset, timeOffset, curTime;
    int remainder;
    int width = this->rect().width();
    QPen tickPen(QColor(190,190,190));
    QPen textPen(QColor(50,50,50));

    //Calculate the expensive mod only once
    remainder = timelineOffset % _tickInterval;
    //The pixel offset for the tick position when scrolling
    pixelOffset = remainder * scale;
    //The timestamp offset for the tick when scrolling
    timeOffset =  timelineOffset - remainder;

    //Draw a tick every 50 msec, with a major tick every half second (or ten ticks)
    //(There seems to be a rounding error that allows us to fit in one more tick,
    //hence the +1)
    numOfTicks = (width / (_tickInterval * scale)) + 1;
    for(i = 0; i < numOfTicks; i++){
        curTickPosition = (i * (_tickInterval * scale)) - pixelOffset;
        //figure out what time is represented by this tick
        curTime = (i * _tickInterval) + timeOffset;

        if(curTime % _majorTickInterval){
            tickHeight = _tickHeight;
        }
        else{
            tickHeight = _majorTickHeight;
            painter->setPen(textPen);
            painter->drawText(curTickPosition - 6, 12, QString::number(MSEC_TO_SECF(curTime)) + "s");
        }

        painter->setPen(tickPen);
        painter->drawLine(curTickPosition,30,curTickPosition,30-tickHeight);
    }
}

void GUITimelineRuler::mousePressEvent(QGraphicsSceneMouseEvent *event){
    int msec = (event->pos().x() / _timeline->curScale()) + _timeline->offset();
    emit rulerClicked(msec);
}

void GUITimelineRuler::mouseMoveEvent(QGraphicsSceneMouseEvent *event){
    int msec = (event->pos().x() / _timeline->curScale()) + _timeline->offset();
    emit mouseMove(msec);
}

void GUITimelineRuler::mouseReleaseEvent(QGraphicsSceneMouseEvent *event){
    emit mouseRelease();
}


/*********************************************************/
/*                GUI MOTION TRACK CLASS                 */
/*********************************************************/
GUIMotionTrack::GUIMotionTrack(RobotMotionTrack* motionTrack, GUIActionTimeline* timeline, QGraphicsItem *parent) :
    GUITimelineTrack(motionTrack, new KeyFrameDialog(), timeline, parent),
    _keyFramePixmap(new QPixmap(":/icons/Key Frame")),
    _keyFrameHoverPixmap(new QPixmap(":/icons/Key Frame Highlight"))
{   
    connect(motionTrack, SIGNAL(motionDurationChanged(int,int)), this, SLOT(onMotionDurationChanged(int,int)));

    //Create items
    createItems();
}

void GUIMotionTrack::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget){
    //Move the track according to the offset
    this->setX((-_timeline->offset() * _timeline->curScale()) + INFO_WIDTH);

    QGraphicsRectItem::paint(painter, option, widget);
}

void GUIMotionTrack::createItems(){
    //Query the robot action for information about the keyframes
    int i, curTimestamp;
    QList<MotionKeyFrame> keyFrames = ((RobotMotionTrack*)_track)->keyFrames();
    QList<QGraphicsItem*> children = this->childItems();
    GUIMotionKeyFrame* curKeyFrame;

    //Delete the old key frames if they exist
    for(i = 0; i < children.count(); i++){
        curKeyFrame = ((GUIMotionKeyFrame*)children[i]);
        scene()->removeItem(curKeyFrame);
        delete curKeyFrame;
    }

    //Create new key frames
    for(i = 0; i < keyFrames.length(); i++){
        //Draw a key frame icon
        curTimestamp = keyFrames[i].timestamp;
        addItem(createItem(curTimestamp));
    }
}

GUIEditableTimelineItem* GUIMotionTrack::createItem(int timestamp){
    GUIMotionKeyFrame* newKeyFrame = new GUIMotionKeyFrame(timestamp, _keyFramePixmap,
                                                              _keyFrameHoverPixmap, _timeline);
    newKeyFrame->setPos(timestamp, 3);
    newKeyFrame->setContextTip("Key Frame");

    return newKeyFrame;
}

void GUIMotionTrack::onItemMovedInGUI(int newTimestamp, int oldTimestamp){
    if(newTimestamp == 0){
        //You cannot replace the timestamp at 0.  Move the item back.
        GUIEditableTimelineItem* movedItem = findItemAtTime(newTimestamp);
        if(movedItem != NULL)
            movedItem->setTimestamp(oldTimestamp);
        return;
    }

    //Tell the action to move the key frame
    if(newTimestamp != oldTimestamp){
        MoveKeyFrameCommand* moveCommand = new MoveKeyFrameCommand(_timeline->action(),
                                                             oldTimestamp, newTimestamp);
        moveCommand->execute();
        moveCommand->setHistoryTip("Move Key Frame");
        History::instance()->push(moveCommand);
    }

    //The key frame was moved to become the new end key frame
    //Recreate all the key frames so that the GUI is updated correctly,
    //with the moved keyframe no longer displayed, etc.
    if(newTimestamp == ((RobotMotionTrack*)_track)->duration())
        createItems();
}

void GUIMotionTrack::onDeleteConfirmed(GUIEditableTimelineItem *item){
    //Bundle up the delete command
    DeleteKeyFrameCommand* deleteCommand = new DeleteKeyFrameCommand(_timeline->action(),
                                                                     item->timestamp());
    deleteCommand->execute();
    deleteCommand->setHistoryTip("Delete Key Frame");
    History::instance()->push(deleteCommand);
}

void GUIMotionTrack::onMotionDurationChanged(int newMsec, int oldMsec){
    qDebug() << "MotionTrack::onMotionDurationChanged: " << newMsec;
    this->setRect(0,0,newMsec,35);

    //Redraw all of the key frames so the GUI is correct
    createItems();
}


/*********************************************************/
/*              GUI MOTION KEY FRAME CLASS               */
/*********************************************************/
GUIMotionKeyFrame::GUIMotionKeyFrame(int timestamp, QPixmap* pixmap, QPixmap* hoverPixmap,
                                     GUIActionTimeline *timeline, QGraphicsItem *parent) :
    GUIEditableTimelineItem(timestamp, timeline, parent),
    _pixmap(pixmap),
    _hoverPixmap(hoverPixmap),
    _hover(false)
{   
    setToolTip(QString::number(MSEC_TO_SECF(_timestamp), 'f', 2) % "s");
    setAcceptHoverEvents(true);
    //This keeps the keyframe at the same scale even though the motion track
    //scales.  Its position stays the same relative to the motion track, though.
    setFlag(QGraphicsItem::ItemIgnoresTransformations);
}

QRectF GUIMotionKeyFrame::boundingRect() const{
    //A negative left side for the bound rect allows us to place the
    //center of the keyframe at its position and draw to the left and right
    return QRectF(-10,0,17,28);
}

void GUIMotionKeyFrame::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget){
    if(_hover)
        painter->drawPixmap(-15,3,30,30,*_hoverPixmap);
    else
        painter->drawPixmap(-15,3,30,30,*_pixmap);
}

void GUIMotionKeyFrame::hoverEnterEvent(QGraphicsSceneHoverEvent *event){
    _hover = true;
    update();
}

void GUIMotionKeyFrame::hoverLeaveEvent(QGraphicsSceneHoverEvent *event){
    _hover = false;
    update();
}

/*********************************************************/
/*                 GUI SOUNDTRACK  CLASS                 */
/*********************************************************/
GUISoundTrack::GUISoundTrack(SoundTrack* soundTrack, GUIActionTimeline* timeline, QGraphicsItem* parent):
    GUITimelineTrack(soundTrack, new AudioDialog(), timeline, parent)
{
    connect(soundTrack, SIGNAL(soundDurationChanged(int,int)), this, SLOT(onSoundDurationChanged(int,int)));
    createItems();
}

void GUISoundTrack::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget){
    this->setX((-_timeline->offset() * _timeline->curScale()) + INFO_WIDTH);
}

void GUISoundTrack::createItems(){
    int i;
    QList<SoundInfo> soundInformation = ((SoundTrack*)_track)->soundInformation();
    QList<QGraphicsItem*> children = this->childItems();
    GUISound* curSound;

    for(i = 0; i < children.count(); i++){
        curSound = ((GUISound*)children[i]);
        scene()->removeItem(curSound);
        delete curSound;
    }

    //Create all of the sound boxes
    for(int i = 0; i < soundInformation.length(); i++){
        addItem(createGUISound(soundInformation[i].timestamp,
                               soundInformation[i].duration,
                               *(soundInformation[i].filename)));
    }
}

GUIEditableTimelineItem* GUISoundTrack::createItem(int timestamp){
    SoundInfo sound = ((SoundTrack*)_track)->soundAt(timestamp);
    if(sound.timestamp != -1)
        return createGUISound(timestamp, sound.duration, *(sound.filename));

    return NULL;
}

GUIEditableTimelineItem* GUISoundTrack::createGUISound(int timestamp, int duration, QString filename){
    GUISound* curSound;
    QColor soundColor(173,216,230,100);
    QBrush soundBrush(soundColor);

    curSound = new GUISound(timestamp, filename, duration, _timeline);
    curSound->setPos(timestamp, 0);
    curSound->setPen(QPen(QColor(190,190,190)));
    curSound->setBrush(soundBrush);
    curSound->setContextTip("Sound");

    curSound->setLowerMovementBound(GUIEditableTimelineItem::SCROLL_PAST_VISIBLE);

    return curSound;
}

void GUISoundTrack::onItemMovedInGUI(int newTimestamp, int oldTimestamp){
    //Tell the action to move the sound
    if(newTimestamp != oldTimestamp){
        MoveSoundCommand* moveCommand = new MoveSoundCommand(_timeline->action(),
                                                             oldTimestamp, newTimestamp);
        moveCommand->execute();
        moveCommand->setHistoryTip("Move Sound");
        History::instance()->push(moveCommand);
    }
}

void GUISoundTrack::onDeleteConfirmed(GUIEditableTimelineItem *item){
    //Bundle up the delete command
    DeleteSoundCommand* deleteCommand = new DeleteSoundCommand(_timeline->action(),item->timestamp());
    deleteCommand->execute();
    deleteCommand->setHistoryTip("Delete Sound");
    History::instance()->push(deleteCommand);
}


void GUISoundTrack::onSoundDurationChanged(int msec, int newDuration){
    //Find the child item
    QList<QGraphicsItem*> children = this->childItems();
    for(int i = 0; i < children.count(); i++){
        if(((GUISound*)children[i])->timestamp() == msec){
            ((GUISound*)children[i])->setDuration(newDuration);
            return;
        }
    }
}


/*********************************************************/
/*                    GUI SOUND  CLASS                   */
/*********************************************************/
GUISound::GUISound(int timestamp, QString filename, int duration,
                         GUIActionTimeline *timeline, QGraphicsItem *parent):
    GUIEditableTimelineItem(timestamp, timeline, parent),
    _duration(duration),
    _durationBeforeTrim(duration),
    _trimDirection(NONE),
    _trimGroupCommand(NULL)
{
    QFileInfo file(filename);
    _filename = file.fileName();

    setToolTip(_filename);
    setAcceptHoverEvents(true);
}

void GUISound::setDuration(int msec){
    _duration = msec;
    //Redraw
    this->update();
}

int GUISound::duration() const{
    return _duration;
}

SoundInfo GUISound::getSoundInfo() const{
    return ((SoundTrack*)(((GUISoundTrack*)this->parentItem())->_track))->soundAt(_timestamp);
}

QRectF GUISound::boundingRect() const{
    //Return a rectangle, starting at 0,0 and going the duration of the sound.
    //However, we need to compensate for the pen on both ends, so we go
    //negative half of the pen width to the left.  There is no pen on the top
    //and bottom
    float penWidth = pen().widthF();
    return QRectF(-penWidth / 2, 0, _duration + penWidth, 35);
}


void GUISound::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget){
    //Paint the rectangle
    painter->setPen(Qt::NoPen);
    painter->setBrush(brush());
    painter->drawRect(0,0,_duration,35);
    //Paint the borders on the side, but not the top
    painter->setPen(pen());
    painter->drawLine(0,0,0,35);
    painter->drawLine(_duration,0,_duration,35);

    if(_trimming){
        QBrush curBrush = brush();
        QColor color = curBrush.color();
        color.setAlpha(255);
        curBrush.setColor(color);
        painter->setBrush(curBrush);

        SoundInfo sound = getSoundInfo();
        QRect rect(-sound.trimStart, 30, sound.mediaDuration, 5);
        painter->drawRect(rect);

    }

    //Paint the text
    //Resetting the transform removes the scaling affect for the text, but
    //we need to specify scene coordinates for the drawing
    int textPos = INFO_WIDTH + (_timestamp - _timeline->offset()) * _timeline->curScale() + 10;
    painter->resetTransform();
    painter->setPen(QColor(70,70,70));
    painter->drawText(textPos, this->scenePos().y() + 15,_filename);
}

void GUISound::mousePressEvent(QGraphicsSceneMouseEvent *event){
    if(_trimDirection){
        _durationBeforeTrim = _duration;
        _trimming = true;
    }

    GUIEditableTimelineItem::mousePressEvent(event);
}

void GUISound::mouseMoveEvent(QGraphicsSceneMouseEvent *event){
    if(_trimDirection){
        if(_trimGroupCommand == NULL){
            _trimGroupCommand = new ActionCommandGroup(_timeline->action());
            _trimGroupCommand->setHistoryTip("Trim Sound");
        }

        TrimSoundCommand* trimCommand = NULL;
        int trimAmount;
        if(_trimDirection == LEFT){
            //Trim from the front (IT'S SO SWEET THAT THE GUI UPDATES CORRECTLY BY ITSELF)
            trimAmount = event->pos().x();
            //Only trim in increments of 10 msec
            if(abs(trimAmount) >= 10){
                trimAmount = ROUND_TO(trimAmount, 10);
                trimCommand = new TrimSoundCommand(_timeline->action(), _timestamp,
                                                   trimAmount,0);
            }
        }
        else if(_trimDirection == RIGHT){
            //Trim from the back
            trimAmount = _duration - event->pos().x();

            if(abs(trimAmount) >= 10){
                trimAmount = ROUND_TO(trimAmount, 10);
                trimCommand = new TrimSoundCommand(_timeline->action(), _timestamp, 0,
                                                   trimAmount);
            }
        }

        if(trimCommand != NULL){
            //Execute the command, which will update the model and GUI
            trimCommand->execute();

            //Find out the real trim amount (in case it was corrected for
            //media boundaries, etc.)
            trimAmount = -(_durationBeforeTrim - _duration);

            //Display a tooltip with information about how far the item is
            //being dragged along the timeline
            QString sign = (trimAmount >= 0) ? "Trim (+" : "Trim (";
            QString infoText = sign % QString::number(MSEC_TO_SECF(trimAmount), 'f', 2) % "s)";

            QToolTip::showText(event->screenPos(), infoText);

            _trimGroupCommand->append(trimCommand);
        }
    }
    else
        GUIEditableTimelineItem::mouseMoveEvent(event);
}

void GUISound::mouseReleaseEvent(QGraphicsSceneMouseEvent *event){
    if(_trimDirection){
        //Place the trim command group in the history, so that
        //it undoes/redoes the entire drag
        if(_trimGroupCommand)
            History::instance()->push(_trimGroupCommand);

        _trimGroupCommand = NULL;
        _trimDirection = NONE;
        _durationBeforeTrim = _duration;
        _trimming = false;

        update();
    }
    else
        GUIEditableTimelineItem::mouseReleaseEvent(event);
}

void GUISound::hoverMoveEvent(QGraphicsSceneHoverEvent *event){
    //If the hover enters from the left or right edge of the
    //sound box, change the cursor to indicate you can drag to
    //clip the sound, rather than move it around.
    QRectF rightBoundRect((_duration - (10 / _timeline->curScale())), 0, (10 / _timeline->curScale()), 35);
    QRectF leftBoundRect(0,0,(10 / _timeline->curScale()),35);
    QPointF cursorPosition = event->pos();

    if(rightBoundRect.contains(cursorPosition)){
        this->setCursor(QCursor(Qt::SplitHCursor));
        _trimDirection = RIGHT;
    }
    else if(leftBoundRect.contains(cursorPosition)){
        this->setCursor(QCursor(Qt::SplitHCursor));
        _trimDirection = LEFT;
    }
    else{
        this->setCursor(QCursor(Qt::ArrowCursor));
        _trimDirection = NONE;
    }

}

